/*
 * Copyright 2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.seppiko.chart.utils;

import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.encoder.ByteMatrix;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.Writer;

/**
 * @author Leonard Woo
 */
public class ZxingImageUtil {

  public static final Integer BLACK = 0x000000;
  public static final Integer WHITE = 0xFFFFFF;

  /*
   * Note:
   * That the input matrix uses 0 == white, 1 == black, while the output
   * matrix uses 0 == black, 255 == white (i.e. an 8 bit greyscale bitmap).
   */

  /**
   * @param input Byte Matrix
   * @return Image Object
   */
  public static BufferedImage toBufferedImage(ByteMatrix input, int height, int width, int margin, int color, int backgroundColor) {
    int inputWidth = input.getWidth();
    int inputHeight = input.getHeight();
    int qrWidth = inputWidth + margin * 2;
    int qrHeight = inputHeight + margin * 2;

    int outputWidth = Math.max(width, qrWidth);
    int outputHeight = Math.max(height, qrHeight);
    int multiple = Math.min(outputWidth / qrWidth, outputHeight / qrHeight);

    BitMatrix output = toBitMatrix(input, (byte) 1, outputHeight, outputWidth, multiple, multiple);

    return toBufferedImage(output, backgroundColor, color);
  }

  public static BufferedImage toBufferedImage(BitMatrix matrix, int backgroundColor, int color) {
    int width = matrix.getWidth();
    int height = matrix.getHeight();
    BufferedImage image = new BufferedImage(width, height,
        (color == BLACK && backgroundColor == WHITE) ? BufferedImage.TYPE_BYTE_GRAY : BufferedImage.TYPE_INT_RGB);
    for (int x = 0; x < width; x++) {
      for (int y = 0; y < height; y++) {
        image.setRGB(x, y, matrix.get(x, y) ? color : backgroundColor);
      }
    }
    return image;
  }

  public static BufferedImage toBufferedImage(boolean[] code, int width, int height, int margin, int backgroundColor, int color) {
    return toBufferedImage(renderResult(code, width, height, margin), backgroundColor, color);
  }

  // see com.google.zxing.oned.OneDimensionalCodeWriter.renderResult
  private static BitMatrix renderResult(boolean[] code, int width, int height, int margin) {
    int inputWidth = code.length;
    int fullWidth = inputWidth + margin;
    int outputWidth = Math.max(width, fullWidth);
    int outputHeight = Math.max(1, height);
    int multiple = outputWidth / fullWidth;
    int leftPadding = (outputWidth - inputWidth * multiple) / 2;
    BitMatrix output = new BitMatrix(outputWidth, outputHeight);
    int inputX = 0;

    for(int outputX = leftPadding; inputX < inputWidth; outputX += multiple) {
      if (code[inputX]) {
        output.setRegion(outputX, 0, multiple, outputHeight);
      }
      ++inputX;
    }
    return output;
  }

  private static BitMatrix toBitMatrix(ByteMatrix matrix, byte flag, int height, int width, int cellHeight, int cellWidth) {
    int inputWidth = matrix.getWidth();
    int inputHeight = matrix.getHeight();
    int leftPadding = (width - inputWidth * cellWidth) / 2;
    int topPadding = (height - inputHeight * cellHeight) / 2;

    BitMatrix output = new BitMatrix(width, height);
    int inputY = 0;
    for (int outputY = topPadding; inputY < inputHeight; outputY += cellHeight) {
      int inputX = 0;
      for (int outputX = leftPadding; inputX < inputWidth; outputX += cellWidth) {
        if (matrix.get(inputX, inputY) == flag) {
          output.setRegion(outputX, outputY, cellWidth, cellHeight);
        }
        ++inputX;
      }
      ++inputY;
    }

    return output;
  }

  public static void toSVGDocument(ByteMatrix matrix, String content, Writer out, int width, int height, int margin, int color, int backgroundColor) {
    SVGUtil.toSVGDocument(matrix, content, out, width, height, margin, color, backgroundColor);
  }

  public static void toSVGDocument(boolean[] code, Writer out, String content, int width, int height, int margin, int color, int backgroundColor) {
    SVGUtil.toSVGDocument(code, out, content, width, height, margin, color, backgroundColor);
  }


  public static BufferedImage addTextOnBottom(BufferedImage image, String content, int margin) {
    return addContent(image, new Font("Arial", Font.PLAIN, 18), Color.BLACK, content, margin);
  }

  public static BufferedImage addContent(BufferedImage src, Font font, Color color, String content, int margin) {
    int height = src.getHeight();
    int width = src.getWidth();
    int dpi = 1;

    BufferedImage out = new BufferedImage(width + (margin * 2), height + (font.getSize() * dpi) + (margin * 2), src.getType());
    Graphics2D g = out.createGraphics();

    g.setColor(Color.WHITE);
    g.fillRect(0,0, out.getWidth(), out.getHeight());

    g.drawImage(src, margin, margin, width, height, null);
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,  RenderingHints.VALUE_ANTIALIAS_ON);
    g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

    g.setColor(color);
    g.setFont(font);

    FontMetrics fm = g.getFontMetrics(font);
    int textWidth = fm.stringWidth(content);
    while (textWidth > width) {
      font = new Font(font.getFamily(), font.getStyle(), font.getSize() - 1);
      g.setFont(font);
      fm = g.getFontMetrics(font);
      textWidth = fm.stringWidth(content);
    }

    // vertical: bottom; align: center;
    int x = margin + ((width - textWidth) / 2);
    int y = margin + height + font.getSize();

    g.drawString(content, x, y);

    g.dispose();

    return out;
  }

}
